﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Liphofra.Core
{
    /// <summary>
    /// An implementation of a simple IoC container for Windows Phone.
    /// </summary>
    /// <remarks>
    /// Recursively resolving dependencies currently only supports a single constructor on the involved types.
    /// </remarks>
    public class ServiceLocator : IServiceLocator
    {
        private static readonly IServiceLocator _instance = new ServiceLocator();

        private readonly Dictionary<Type, Func<object>> _mappings = new Dictionary<Type, Func<object>>();

        /// <summary>
        /// Gets the singleton instance of the service locator.
        /// </summary>
        public static IServiceLocator Instance
        {
            get
            {
                return _instance;
            }
        }

        private ServiceLocator()
        {
        }

        /// <summary>
        /// Registers a mapping between interface and corresponding implementation.
        /// </summary>
        /// <typeparam name="TInterface">The type of the interface.</typeparam>
        /// <typeparam name="TImplementation">The type of the implementation.</typeparam>
        public void Register<TInterface, TImplementation>()
        {
            var typeSource = typeof(TInterface);
            var typeTarget = typeof(TImplementation);

            _mappings[typeSource] = () => CreateInstance(typeTarget);
        }

        /// <summary>
        /// Registers the specified instance for the given type. This instance will be returned for this type for successive calls of <see cref="Resolve{T}" /> or <see cref="Resolve" />.
        /// </summary>
        /// <typeparam name="T">The type to register this instance for.</typeparam>
        /// <param name="instance">The instance to register for the given type.</param>
        public void Register<T>(T instance)
        {
            var typeSource = typeof(T);
            _mappings[typeSource] = () => instance;
        }

        /// <summary>
        /// Registers the specified function as mechanism to gain access to an instance of type <typeparamref name="T" />. This will be used in successive calls of <see cref="Resolve{T}" /> or <see cref="Resolve" />.
        /// </summary>
        /// <typeparam name="T">The type the function is registered for.</typeparam>
        /// <param name="func">The function to use to retrieve an instance of <typeparamref name="T" />.</param>
        public void Register<T>(Func<T> func)
        {
            var typeSource = typeof(T);
            _mappings[typeSource] = () => func();
        }

        /// <summary>
        /// Resolves an instance of the given type <typeparamref name="T" />.
        /// </summary>
        /// <typeparam name="T">The type that should be resolved.</typeparam>
        /// <returns>
        /// An instance of the requested type retrieved using the previously specified registration, or using its constructor. Dependencies are resolved recursively.
        /// </returns>
        public T Resolve<T>()
        {
            var typeSource = typeof(T);
            var result = Resolve(typeSource);
            if (result != null)
            {
                return (T)result;
            }

            return default(T);
        }

        /// <summary>
        /// Resolves an instance of the specified type.
        /// </summary>
        /// <param name="type">The type to resolve an instance for.</param>
        /// <returns>
        /// An instance of the requested type retrieved using the previously specified registration, or using its constructor. Dependencies are resolved recursively.
        /// </returns>
        public object Resolve(Type type)
        {
            object result;

            Func<object> func;
            if (_mappings.TryGetValue(type, out func))
            {
                result = func();
            }
            else if (type == typeof(IServiceLocator))
            {
                // if nobody has mapped the service locator interface
                // simply return ourselves for it
                result = this;
            }
            else
            {
                // type hasn't been registered before
                // simply try to create an instance directly
                result = CreateInstance(type);
            }

            return result;
        }

        private object CreateInstance(Type type)
        {
            // check if it has a parameterless constructor, and use that
            var constructors = type.GetConstructors();

            // we use > 1 because interfaces have zero :)
            if (constructors.Length != 1)
            {
                throw new NotSupportedException("The service locator currently only supports types with a single constructor.");
            }

            var constructor = constructors.First();

            var constructorParameters = constructor.GetParameters();
            var parameters = constructorParameters.Length > 0 ? new object[constructorParameters.Length] : null;
            for (var i = 0; i < constructorParameters.Length; i++)
            {
                var parameterType = constructorParameters[i].ParameterType;
                var instance = Resolve(parameterType);
                parameters[i] = instance;
            }

            var result = constructor.Invoke(parameters);
            return result;
        }
    }
}